Cross-Origin Resource Sharing (CORS) in APIs
Learn how APIs interact with different domains using cross-origin resource sharing.
Background#
With the introduction of JavaScript and Document Object Model (DOM) in web browsers, manipulating an HTML document's objects and properties using JavaScript became possible. As a result, a malicious script loaded by one web page could interact with the resources from another web page and retrieve sensitive information using the latter's DOM. This vulnerability of the DOM could be exploited by forgery attacks, such as a CSRF attack, through which attackers could gain unauthorized access to different resources. Here is an example scenario:
Suppose that John is lured into visiting www.evil-site.com. This site responds with JavaScript code that then makes a call to www.facebook.com, where John logs in without any hesitation. As a consequence, the JavaScript code downloaded from www.evil-site.com obtains access to the DOM elements of www.facebook.com and, by virtue, to John's sensitive data.
The origin problem#
The example that we saw above demonstrates an unrestricted interaction between two web pages belonging to different origins, which could lead to a potential data breach. An origin is defined as a combination of scheme (protocol), hostname, and port number (if specified). Two URLs are said to have the same origin if and only if they have the same schemes, hostnames, and port numbers.
URLs with different hostnames belong to different origins. A request that spans across different origins is known as a cross-origin request.
The following code compares the origins of two URLs. Try changing the URLs to see if they have the same origin.
The same-origin policy (SOP)#
To combat this susceptibility and reduce attack vectors, the engineers at Netscape defined a rule called the same-origin policy (SOP), whereby two web pages can only interact with each other if they belong to the same origin. Simply put, the URL www.evil-site.com can’t access the resources of www.facebook.com since they both have different origins.
The illustration below depicts a same-origin request, where the URL http://abc.com retrieves an image it needs to display by requesting data from its own web server.
Since the origin of the web document and the requested image is the same, the request will be successful. If a server only allows requests from its origin, it follows the SOP.
The cross-origin resource sharing (CORS) policy#
However, in today's world, it isn't feasible to not allow requests from other origins. The SOP is too restrictive, so we need a method that relaxes the constraints imposed by SOP. One such technique is cross-origin resource sharing (CORS), which allows websites to bypass the SOP by going through checks. CORS employs an HTTP header-based mechanism to inform the web browser whether an application belonging to one origin can access resources from a server at a different origin.
CORS allows two types of cross-origin requests: simple and preflighted. Let's begin with simple requests.
Simple requests#
A simple request meets all the following conditions:
The request must have an HTTP method.
The request should have headers that can be defined manually in header variables.
The only allowed values for the
Content-typeheaders should be defined in the request.The upload request object has no registered event listeners.
No ReadableStream object is used in the request.
The process begins with a cross-origin request initiated by http://abc.com for data stored in the http://xyz.com server. In this example, while http://abc.com is a web service, in our context, it will act as a client. The request contains the client's Origin. A response is then sent from the server to the browser. The server adds an Access-Control-Allow-Origin header to the response. The browser then compares the Origin in the client's request with Access-Control-Allow-Origin in the server's response. If they are identical, the cross-origin request is permitted. An example of this situation is shown below:
As we can see in the example above, the application's Origin: http://abc.com is present in the server's Access-Control-Allow-Origin header, which means the cross-origin request will be approved.
It may be helpful for us to see the structure of these requests and responses. Here is what the CORS request would look like:
The server may send the following response to the request mentioned above:
Because the Origin header in the request and the Access-Control-Allow-Origin header in the response match, the request will be approved. In comparison, let's look at a server response where an origin is not allowed to access the resource:
In this case, the Origin and Access-Control-Allow-Origin headers don't match; therefore, the request will not be approved. In such a case, the browser won't allow the data to be shared with the client. This results in an error on the client side, and for security reasons (to prevent attackers from exploiting CORS), not much information is given on what went wrong.
Question
Is there a way to allow cross-origin requests from all the origins?
Yes. In very specific cases, the Access-Control-Allow-Origin header may be set to *, which allows any Origin to make the request. This case is known as a wildcard, a keyword that depicts this situation. This cannot be allowed in the real world because of security issues that arise if private data is shared and because most browsers don’t support this wildcard.
Preflight requests#
If a request does not fulfill the conditions for a simple request, it needs to be preflighted. This protocol was given this name because it functions similarly to how passengers must be preflighted before they can onboard the plane. Preflighting our request includes an initial check, which ensures the request is safe to be sent to the server. A common reason for preflighting is that the requests may alter the user's data (this could be on both the client and server side).
The check would be as follows:
The browser already knows what requests to preflight and makes an initial request to the server seeking permission to employ the methods. This is done through the
OPTIONSmethod, which specifies the client'sOriginandAccess-Control-Request-Method(the HTTP request methods it requires, such asPOST). This is known as a preflight call. Here is how the preflight request looks in the code:
If the server accepts the
OPTIONSrequest, it responds by allowing the client to make requests with the methods they requested. These methods are specified in theAccess-Control-Allow-Methodheader. The response header by the server may look something like this:
After the successful completion of the preflight check, the CORS request can be sent by the client
http://abc.comas before. However, this time, the client is allowed to define custom headers or use other defined values for different headers. For example, in this case, the client could set theContent-typeheader toapplication/xml. The request would look something like this:
The server then responds to the CORS request as it would do normally.
Note: To prevent further overhead in our API, we can cache the preflight for a certain amount of time to prevent unnecessary server requests, which could lead to a potential bottleneck. This means multiple requests can be made to the API in the specified time frame. This is provided in the Access-Control-Max-Age header that’s provided by the server, which stores the maximum time the request can be cached for.Authentication in preflight requests#
By default, both simple and preflight requests don't include authentication through credentials. This can be added by the authorization header in the request or take the form of an authorization cookie. To enable this, we must manually specify the flag withCredentials = true for the request. The Access-Control-Allow-Credentials header in the server's response must also be set as true. Authentication with an authorization cookie is shown in the illustration below:
CORS vulnerabilities#
Even though many APIs and applications implement CORS to allow communications and access from third parties, vulnerabilities may arise. These vulnerabilities typically stem from the lackluster implementation of CORS without taking the proper security measures. Some major factors that lead to CORS being vulnerable are given below:
Third-party verification (for clients who use the APIs) is not strict enough. Many APIs may ease their verification to ensure everything functions, which allows attackers to exploit this lack of scrutiny.
APIs allow access from any other origin. This is typically done to streamline the implementation so that the APIs don't need to maintain a list of the approved parties. This allows malicious origins to access the sensitive resources in the API.
If an API trusts an origin that is susceptible to injection attacks, an attacker could inject malicious code that utilizes CORS to gain access to sensitive information.
Therefore, with all these vulnerabilities, we should have measures that mitigate these damages.
Prevention of vulnerabilities#
As we can see from the prior section, almost all of these vulnerabilities stem from the incorrect implementation of CORS. So, if we're stringent when we implement CORS in our applications, we should avoid most problems. This can be done in the following ways:
Stringent verification of all origins. There's a direct correlation between an API's leniency in checking origins and the exploitations that may occur.
Only allow listed and trusted origins, and avoid access to all subdomains of an entity because attackers may exploit this.
Avoid using wildcards on networks, sites, and URLs. It will allow access to malicious origins that may gain access to resources. A list of trusted origins should be maintained even if it increases the complexity of the implementation.
Server-side validation should still be implemented. CORS isn't a substitute for this validation, and any malicious entity that bypasses this should be caught by security mechanisms implemented on the server side. This includes validation against the injection attacks we mentioned in an earlier lesson.
In terms of API security, CORS is a robust option because often, most of our requests are from external sources. It's flexible and allows options for security, such as the wildcard and preflighting, and is considered a boost in security in terms of API design.
Quiz
(True or False) The Access-Control-Allow-Origin = * allows for specific origins to make a request.
True
False
The code above is a wildcard, which allows any origin to make the request.
Now, let’s take a look at authentication and authorization, two core security concepts, in the next lesson.
Securing APIs Using Input Validation
Authentication and Authorization